/*
 * Video.js Hotkeys
 * https://github.com/ctd1500/videojs-hotkeys
 *
 * Copyright (c) 2015 Chris Dougherty
 * Licensed under the Apache-2.0 license.
 */

(function (root, factory) {
	if (typeof window !== "undefined" && window.videojs) {
		factory(window.videojs);
	} else if (typeof define === "function" && define.amd) {
		define("videojs-hotkeys", ["video.js"], function (module) {
			return factory(module.default || module);
		});
	} else if (typeof module !== "undefined" && module.exports) {
		var videojs = require("video.js");
		module.exports = factory(videojs.default || videojs);
	}
})(this, function (videojs) {
	"use strict";
	if (typeof window !== "undefined") {
		window["videojs_hotkeys"] = { version: "0.2.30" };
	}

	var hotkeys = function (options) {
		var player = this;
		var pEl = player.el();
		var doc = document;
		var def_options = {
			volumeStep: 0.1,
			seekStep: 5,
			enableMute: true,
			enableVolumeScroll: true,
			enableHoverScroll: false,
			enableFullscreen: true,
			enableNumbers: true,
			enableJogStyle: false,
			alwaysCaptureHotkeys: false,
			captureDocumentHotkeys: false,
			documentHotkeysFocusElementFilter: () => {
				return false;
			},
			enableModifiersForNumbers: true,
			enableInactiveFocus: true,
			skipInitialFocus: false,
			playPauseKey: playPauseKey,
			rewindKey: rewindKey,
			forwardKey: forwardKey,
			volumeUpKey: volumeUpKey,
			volumeDownKey: volumeDownKey,
			muteKey: muteKey,
			fullscreenKey: fullscreenKey,
			customKeys: {},
		};

		var cPlay = 1,
			cRewind = 2,
			cForward = 3,
			cVolumeUp = 4,
			cVolumeDown = 5,
			cMute = 6,
			cFullscreen = 7;

		// Use built-in merge function from Video.js v5.0+ or v4.4.0+
		var mergeOptions = videojs.obj?.merge || videojs.mergeOptions || videojs.util.mergeOptions;
		options = mergeOptions(def_options, options || {});

		var volumeStep = options.volumeStep,
			seekStep = options.seekStep,
			enableMute = options.enableMute,
			enableVolumeScroll = options.enableVolumeScroll,
			enableHoverScroll = options.enableHoverScroll,
			enableFull = options.enableFullscreen,
			enableNumbers = options.enableNumbers,
			enableJogStyle = options.enableJogStyle,
			alwaysCaptureHotkeys = options.alwaysCaptureHotkeys,
			captureDocumentHotkeys = options.captureDocumentHotkeys,
			documentHotkeysFocusElementFilter = options.documentHotkeysFocusElementFilter,
			enableModifiersForNumbers = options.enableModifiersForNumbers,
			enableInactiveFocus = options.enableInactiveFocus,
			skipInitialFocus = options.skipInitialFocus;

		var videojsVer = videojs.VERSION;

		// Set default player tabindex to handle keydown and doubleclick events
		if (!pEl.hasAttribute("tabIndex")) {
			pEl.setAttribute("tabIndex", "-1");
		}

		// Remove player outline to fix video performance issue
		pEl.style.outline = "none";

		if (alwaysCaptureHotkeys || !player.autoplay()) {
			if (!skipInitialFocus) {
				player.one("play", () => {
					pEl.focus(); // Fixes the .vjs-big-play-button handing focus back to body instead of the player
				});
			}
		}

		if (enableInactiveFocus) {
			player.on("userinactive", () => {
				// When the control bar fades, re-apply focus to the player if last focus was a control button
				var cancelFocusingPlayer = () => {
					clearTimeout(focusingPlayerTimeout);
				};
				var focusingPlayerTimeout = setTimeout(() => {
					player.off("useractive", cancelFocusingPlayer);
					var activeElement = doc.activeElement;
					var parentEl = activeElement.parentElement;
					var controlBar = pEl.querySelector(".vjs-control-bar");
					if (activeElement && (controlBar == parentEl || controlBar == parentEl.parentElement)) {
						pEl.focus();
					}
				}, 10);

				player.one("useractive", cancelFocusingPlayer);
			});
		}

		player.on("play", () => {
			// Fix allowing the YouTube plugin to have hotkey support.
			var ifblocker = pEl.querySelector(".iframeblocker");
			if (ifblocker && ifblocker.style.display === "") {
				ifblocker.style.display = "block";
				ifblocker.style.bottom = "39px";
			}
		});

		var keyDown = function keyDown(event) {
			var ewhich = event.which,
				wasPlaying,
				seekTime;
			var ePreventDefault = event.preventDefault.bind(event);
			var duration = player.duration();
			// When controls are disabled, hotkeys will be disabled as well
			if (player.controls()) {
				// Don't catch keys if any control buttons are focused, unless alwaysCaptureHotkeys is true
				var activeEl = doc.activeElement;
				if (
					alwaysCaptureHotkeys ||
					(captureDocumentHotkeys && documentHotkeysFocusElementFilter(activeEl)) ||
					activeEl == pEl ||
					activeEl == pEl.querySelector(".vjs-tech") ||
					activeEl == pEl.querySelector(".vjs-control-bar") ||
					activeEl == pEl.querySelector(".iframeblocker")
				) {
					switch (checkKeys(event, player)) {
						// Spacebar toggles play/pause
						case cPlay:
							ePreventDefault();
							if (alwaysCaptureHotkeys || captureDocumentHotkeys) {
								// Prevent control activation with space
								event.stopPropagation();
							}

							if (player.paused()) {
								silencePromise(player.play());
							} else {
								player.pause();
							}
							break;

						// Seeking with the left/right arrow keys
						case cRewind: // Seek Backward
							wasPlaying = !player.paused();
							ePreventDefault();
							if (wasPlaying) {
								player.pause();
							}
							seekTime = player.currentTime() - seekStepD(event);
							// The flash player tech will allow you to seek into negative
							// numbers and break the seekbar, so try to prevent that.
							if (seekTime <= 0) {
								seekTime = 0;
							}
							player.currentTime(seekTime);
							if (wasPlaying) {
								silencePromise(player.play());
							}
							break;
						case cForward: // Seek Forward
							wasPlaying = !player.paused();
							ePreventDefault();
							if (wasPlaying) {
								player.pause();
							}
							seekTime = player.currentTime() + seekStepD(event);
							// Fixes the player not sending the end event if you
							// try to seek past the duration on the seekbar.
							if (seekTime >= duration) {
								seekTime = wasPlaying ? duration - 0.001 : duration;
							}
							player.currentTime(seekTime);
							if (wasPlaying) {
								silencePromise(player.play());
							}
							break;

						// Volume control with the up/down arrow keys
						case cVolumeDown:
							ePreventDefault();
							if (!enableJogStyle) {
								player.volume(player.volume() - volumeStep);
							} else {
								seekTime = player.currentTime() - 1;
								if (player.currentTime() <= 1) {
									seekTime = 0;
								}
								player.currentTime(seekTime);
							}
							break;
						case cVolumeUp:
							ePreventDefault();
							if (!enableJogStyle) {
								player.volume(player.volume() + volumeStep);
							} else {
								seekTime = player.currentTime() + 1;
								if (seekTime >= duration) {
									seekTime = duration;
								}
								player.currentTime(seekTime);
							}
							break;

						// Toggle Mute with the M key
						case cMute:
							if (enableMute) {
								player.muted(!player.muted());
							}
							break;

						// Toggle Fullscreen with the F key
						case cFullscreen:
							if (enableFull) {
								if (player.isFullscreen()) {
									player.exitFullscreen();
								} else {
									player.requestFullscreen();
								}
							}
							break;

						default:
							// Number keys from 0-9 skip to a percentage of the video. 0 is 0% and 9 is 90%
							if ((ewhich > 47 && ewhich < 59) || (ewhich > 95 && ewhich < 106)) {
								// Do not handle if enableModifiersForNumbers set to false and keys are Ctrl, Cmd or Alt
								if (enableModifiersForNumbers || !(event.metaKey || event.ctrlKey || event.altKey)) {
									if (enableNumbers) {
										var sub = 48;
										if (ewhich > 95) {
											sub = 96;
										}
										var number = ewhich - sub;
										ePreventDefault();
										player.currentTime(player.duration() * number * 0.1);
									}
								}
							}

							// Handle any custom hotkeys
							for (var customKey in options.customKeys) {
								var customHotkey = options.customKeys[customKey];
								// Check for well formed custom keys
								if (customHotkey && customHotkey.key && customHotkey.handler) {
									// Check if the custom key's condition matches
									if (customHotkey.key(event)) {
										ePreventDefault();
										customHotkey.handler(player, options, event);
									}
								}
							}
					}
				}
			}
		};

		var doubleClick = function doubleClick(event) {
			// Video.js added double-click fullscreen in 7.1.0
			if (videojsVer != null && videojsVer <= "7.1.0") {
				// When controls are disabled, hotkeys will be disabled as well
				if (player.controls()) {
					// Don't catch clicks if any control buttons are focused
					var activeEl = event.relatedTarget || event.toElement || doc.activeElement;
					if (activeEl == pEl || activeEl == pEl.querySelector(".vjs-tech") || activeEl == pEl.querySelector(".iframeblocker")) {
						if (enableFull) {
							if (player.isFullscreen()) {
								player.exitFullscreen();
							} else {
								player.requestFullscreen();
							}
						}
					}
				}
			}
		};

		var volumeHover = false;
		var volumeSelector = pEl.querySelector(".vjs-volume-menu-button") || pEl.querySelector(".vjs-volume-panel");
		if (volumeSelector != null) {
			volumeSelector.addEventListener("mouseover", () => {
				volumeHover = true;
			});
			volumeSelector.addEventListener("mouseout", () => {
				volumeHover = false;
			});
		}

		var mouseScroll = function mouseScroll(event) {
			if (enableHoverScroll) {
				// If we leave this undefined then it can match non-existent elements below
				var activeEl = 0;
			} else {
				var activeEl = doc.activeElement;
			}

			// When controls are disabled, hotkeys will be disabled as well
			if (player.controls()) {
				if (
					alwaysCaptureHotkeys ||
					activeEl == pEl ||
					activeEl == pEl.querySelector(".vjs-tech") ||
					activeEl == pEl.querySelector(".iframeblocker") ||
					activeEl == pEl.querySelector(".vjs-control-bar") ||
					volumeHover
				) {
					if (enableVolumeScroll) {
						event = window.event || event;
						var delta = Math.max(-1, Math.min(1, event.wheelDelta || -event.detail));
						event.preventDefault();

						if (delta == 1) {
							player.volume(player.volume() + volumeStep);
						} else if (delta == -1) {
							player.volume(player.volume() - volumeStep);
						}
					}
				}
			}
		};

		var checkKeys = function checkKeys(e, player) {
			// Allow some modularity in defining custom hotkeys

			// Play/Pause check
			if (options.playPauseKey(e, player)) {
				return cPlay;
			}

			// Seek Backward check
			if (options.rewindKey(e, player)) {
				return cRewind;
			}

			// Seek Forward check
			if (options.forwardKey(e, player)) {
				return cForward;
			}

			// Volume Up check
			if (options.volumeUpKey(e, player)) {
				return cVolumeUp;
			}

			// Volume Down check
			if (options.volumeDownKey(e, player)) {
				return cVolumeDown;
			}

			// Mute check
			if (options.muteKey(e, player)) {
				return cMute;
			}

			// Fullscreen check
			if (options.fullscreenKey(e, player)) {
				return cFullscreen;
			}
		};

		function playPauseKey(e) {
			// Space bar or MediaPlayPause
			return e.which === 32 || e.which === 179;
		}

		function rewindKey(e) {
			// Left Arrow or MediaRewind
			return e.which === 37 || e.which === 177;
		}

		function forwardKey(e) {
			// Right Arrow or MediaForward
			return e.which === 39 || e.which === 176;
		}

		function volumeUpKey(e) {
			// Up Arrow
			return e.which === 38;
		}

		function volumeDownKey(e) {
			// Down Arrow
			return e.which === 40;
		}

		function muteKey(e) {
			// M key
			return e.which === 77;
		}

		function fullscreenKey(e) {
			// F key
			return e.which === 70;
		}

		function seekStepD(e) {
			// SeekStep caller, returns an int, or a function returning an int
			return typeof seekStep === "function" ? seekStep(e) : seekStep;
		}

		function silencePromise(value) {
			if (value != null && typeof value.then === "function") {
				value.then(null, function (e) {});
			}
		}

		if (captureDocumentHotkeys) {
			var capDocHK = function (event) {
				keyDown(event);
			};
			document.addEventListener("keydown", capDocHK);

			this.dispose = () => {
				document.removeEventListener("keydown", capDocHK);
			};
		} else {
			player.on("keydown", keyDown);
		}

		player.on("dblclick", doubleClick);
		player.on("mousewheel", mouseScroll);
		player.on("DOMMouseScroll", mouseScroll);

		return this;
	};

	var registerPlugin = videojs.registerPlugin || videojs.plugin;
	registerPlugin("hotkeys", hotkeys);
});
